package org.jetlinks.pro.things.impl.connector.script;

import org.jetlinks.core.message.ThingMessage;
import org.jetlinks.core.things.ThingProperty;
import org.jetlinks.core.things.ThingType;
import org.jetlinks.core.things.ThingsDataManager;
import org.slf4j.Logger;
import reactor.core.publisher.Mono;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.function.Supplier;

public interface ThingDataHelper {

    Logger getLogger();

    ThingsDataManager getDataManager();

    ThingMessage getMessage();

    long getTimestamp();

    long getTimestamp(String property);

    Object getProperty(String property);

    ThingType getThingType();

    String getThingId();

    //最近属性数据
    default Object $recent(String property) {
        //本次处理包含了此属性值
        Object propertyValue = getProperty(property);
        if (null != propertyValue) {
            return propertyValue;
        }
        return blockGet(() -> "$recent(" + property + ")", this.$recentAsync(property));
    }

    default Object $recent(String thingId, String property) {
        return blockGet(() -> "$recent(" + thingId + "," + property + ")", this.$recentAsync(thingId, property));
    }

    default Mono<Object> $recentAsync(String property) {
        Object propertyValue = getProperty(property);
        if (null != propertyValue) {
            return Mono.just(propertyValue);
        }

        String thingId = getThingId();
        if (thingId == null) {
            return Mono.empty();
        }
        return $recentAsync(thingId, property);
    }

    default Mono<Object> $recentAsync(String thingId, String property) {
        return $lastStateAsync(thingId, property);
    }

    //最新上报数据时间
    default Long $recentTime() {
        return blockGet(() -> "$recentTime()", $recentTimeAsync());
    }

    default Long $recentTime(String thingId) {
        return blockGet(() -> "$recentTime(" + thingId + ")", $recentTimeAsync(thingId));
    }

    default Long $recentTime(String thingId, String property) {
        return blockGet(() -> "$recentTime(" + thingId + "," + property + ")", $recentTimeAsync(thingId, property));
    }

    default Mono<Long> $recentTimeAsync() {
        return $recentTimeAsync(getThingId());
    }

    default Mono<Long> $recentTimeAsync(String thingId) {
        return getDataManager().getLastPropertyTime(getThingType(), thingId, getTimestamp());
    }

    default Mono<Long> $recentTimeAsync(String thingId, String property) {
        return fastMap(
            getDataManager().getLastProperty(getThingType(), thingId, property, getTimestamp(property)),
            ThingProperty::getTimestamp
        );
    }

    //指定设备的属性的最近数据
    default Object $lastState(String thingId, String property) {
        return blockGet(() -> "$lastState(" + thingId + "," + property + ")", this.$lastStateAsync(thingId, property));
    }

    default Object $lastState(String property) {
        return blockGet(() -> "$lastState(" + property + ")", this.$lastStateAsync(property));
    }

    default Object $lastState(String property, long timestamp) {
        return blockGet(() -> "$lastState(" + property + "," + timestamp + ")", this.$lastStateAsync(property, timestamp));
    }

    default Mono<Object> $lastStateAsync(String property, long timestamp) {
        return $lastStateAsync(getThingId(), property, timestamp);
    }

    default Mono<Object> $lastStateAsync(String property) {
        return $lastStateAsync(getThingId(), property);
    }

    default Mono<Object> $lastStateAsync(String thingId, String property) {
        return $lastStateAsync(thingId, property, getTimestamp(property));
    }

    default Mono<Object> $lastStateAsync(String thingId, String property, long timestamp) {
        return fastMap(
            getDataManager().getLastProperty(getThingType(), thingId, property, timestamp),
            ThingProperty::getValue
        );
    }

    //首次属性数据
    default Object $first(String property) {
        return blockGet(() -> "$first(" + property + ")", this.$firstAsync(property));
    }

    //指定设备的属性的最近数据
    default Object $first(String thingId, String property) {
        return blockGet(() -> "$first(" + thingId + "," + property + ")", this.$firstAsync(thingId, property));
    }

    default Mono<Object> $firstAsync(String property) {
        return $firstAsync(getThingId(), property);
    }

    default Mono<Object> $firstAsync(String thingId, String property) {
        return fastMap(
            getDataManager().getFirstProperty(getThingType(), thingId, property),
            ThingProperty::getValue
        );
    }

    //首次上报数据时间
    default Long $firstTime() {
        return blockGet(() -> "$firstTime()", $firstTimeAsync());
    }

    default Long $firstTime(String thingId) {
        return blockGet(() -> "$firstTime(" + thingId + ")", $firstTimeAsync(thingId));
    }

    default Long $firstTime(String thingId, String property) {
        return blockGet(() -> "$firstTime(" + thingId + "," + property + ")", $firstTimeAsync(thingId, property));
    }

    default Mono<Long> $firstTimeAsync() {
        return $firstTimeAsync(getThingId());
    }

    default Mono<Long> $firstTimeAsync(String thingId) {
        return getDataManager().getFirstPropertyTime(getThingType(), thingId);
    }

    default Mono<Long> $firstTimeAsync(String thingId, String property) {
        return fastMap(
            getDataManager().getFirstProperty(getThingType(), thingId, property),
            ThingProperty::getTimestamp
        );
    }

    default <T> T blockGet(Supplier<String> operation, Mono<T> async) {
        try {
            //fast get
            if (async instanceof Callable) {
                return (T) ((Callable<?>) async).call();
            }
            return async
                .toFuture()
                .get(10, TimeUnit.SECONDS);
        } catch (Throwable e) {
            getLogger().warn("{} error", operation.get(), e);
        }
        return null;
    }

    static <T, R> Mono<R> fastMap(Mono<T> source, Function<T, R> mapper) {
        try {
            if (source instanceof Callable) {
                T value = (T) ((Callable<?>) source).call();
                if (value == null) {
                    return Mono.empty();
                }
                return Mono.just(mapper.apply(value));
            }
        } catch (Throwable ignore) {

        }
        return source.map(mapper);
    }
}
